#include <windows.h>

#include "lib.h"
#include "filestream.h"
using namespace Library;

#define SUPER	Stream
#define SELF	FileStream

/**
 * @param filename	A pointer to a null-terminated string that specifies the
 *					desired file to be created or opened.
 * @param mode		Specifies the type of access to the file.  See
 *					Stream::OpenMode for a list of valid mode values.
 */
FileStream::FileStream()
	: SUPER()
{
	m_fileHandle	= INVALID_HANDLE_VALUE;
	m_mode			= 0;
}

FileStream::~FileStream()
{
	close();
}

/**
 * @param buffer	Pointer to the buffer that receives the data read from the
 *					file.
 * @param length	Specifies the number of bytes to be read from the file.
 * @return			The actual number of bytes read or Stream::eof on
 *					error.
 */
int32
FileStream::read(void * buffer, int32 length)
{
	DWORD numRead;

	if (!buffer || length < 0 || m_fileHandle == INVALID_HANDLE_VALUE)
		return Stream::eof;

	// Although a read of zero is non-standard, it's also not an error.
	if (length == 0)
		return 0;

	if (ReadFile(m_fileHandle, buffer, length, &numRead, (LPOVERLAPPED)0))
	{
		if (numRead == 0)
			return Stream::eof;

		return (int32)numRead;
	}

	return 0;
}

/**
 * @param buffer	Pointer to the buffer containing the data to be written
 *					to the file.
 * @param length	Specifies the number of bytes to write to the file.
 * @return			The actual number of bytes written or Stream::eof
 *					on error.
 */
int32
FileStream::write(void * buffer, int32 length)
{
	DWORD numWritten;

	if (!buffer || length < 0 || m_fileHandle == INVALID_HANDLE_VALUE)
		return Stream::eof;

	if (length == 0)
		return 0;

	if (m_mode & FileStream::appending)
		SetFilePointer(m_fileHandle, 0, (LPLONG)0, FILE_END);

	if (WriteFile(m_fileHandle, buffer, (DWORD)length, &numWritten,
		(LPOVERLAPPED)0))
		return (int32)numWritten;
	else
		return Stream::eof;
}

/**
 * @param offset	The number of bytes the file pointer should be moved.
 * @param from		The location from which @e offset should move.  See
 *					Stream::SeekFrom for valid locations.
 * @return			The new file pointer on success or Stream::eof
 *					on error.
 */
int32
FileStream::seek(int32 offset, SeekFrom from)
{
	DWORD	result;

	if (m_fileHandle == INVALID_HANDLE_VALUE)
		return Stream::eof;

	switch (from)
	{
		case Stream::beginning:
			result = SetFilePointer(m_fileHandle, (LONG)offset,
				(PLONG)0, FILE_BEGIN);
			break;
		case Stream::current:
			result = SetFilePointer(m_fileHandle, (LONG)offset,
				(PLONG)0, FILE_CURRENT);
			break;
		case Stream::end:
			result = SetFilePointer(m_fileHandle, (LONG)offset,
				(PLONG)0, FILE_END);
			break;
		default:
			result = 0xFFFFFFFFL;
			break;
	}

	if (result == 0xFFFFFFFFL)
		return Stream::eof;

	return (int32)result;
}

/**
 * @return			The current file pointer on success or Stream::eof
 *					on error.
 */
int32
FileStream::tell()
{
	DWORD result;

	if (m_fileHandle == INVALID_HANDLE_VALUE)
		return Stream::eof;

	result = SetFilePointer(m_fileHandle, 0, (LPLONG)0, FILE_CURRENT);
	if (result == 0xFFFFFFFFL)
		return Stream::eof;

	return (int32)result;
}

bool8
FileStream::open(const String & filename, OpenMode mode)
{
	DWORD accessMode = 0;
	DWORD shareMode  = 0;
	DWORD createMode = 0;

	if (mode & FileStream::reading)
	{
		accessMode	|= GENERIC_READ;
		createMode	= OPEN_EXISTING;
		shareMode	= FILE_SHARE_READ;
	}

	if (mode & FileStream::writing)
	{
		accessMode	|= GENERIC_WRITE;
		createMode	= CREATE_ALWAYS;
		shareMode	= 0;
	}

	if (mode & FileStream::appending)
	{
		accessMode	|= GENERIC_WRITE;
		createMode	= OPEN_ALWAYS;
		shareMode	= 0;
	}

	// Close any previously opened file.
	if (close() == false)
		return false;

	m_fileHandle = CreateFile(filename, accessMode, shareMode,
		(LPSECURITY_ATTRIBUTES)0, createMode, FILE_ATTRIBUTE_NORMAL, (HANDLE)0);

	m_mode = mode;

	return (bool8)(m_fileHandle != INVALID_HANDLE_VALUE);
}

bool8
FileStream::close()
{
	bool8	success = true;

	if (m_fileHandle != INVALID_HANDLE_VALUE)
	{
		success = (bool8)(CloseHandle(m_fileHandle) != 0);
		m_fileHandle = INVALID_HANDLE_VALUE;
	}

	return success;
}
